package com.ihr360.commons.specification;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Ihr360Specification<T> implements Specification<T>, Serializable {

    private static final String TABLE_AND_FIELD_SEPARATER = ".";

    private static final String TABLE_AND_FIELD_SPLIT = "\\.";

    private List<Ihr360Predication> predications;

    public List<Ihr360Predication> getPredications() {
        return predications;
    }

    public void setPredications(List<Ihr360Predication> predications) {
        this.predications = predications;
    }

    private boolean hasField(Ihr360Predication predication) {
        return !StringUtils.isEmpty(predication.getFieldName());
    }

    private Class getFiledClass(Object value, Ihr360EntityFieldType type) {
        if (type == null && value != null) {
            if (String.class.equals(value.getClass())) {
                return String.class;
            }
            if (Boolean.class.equals(value.getClass())) {
                return Boolean.class;
            }
            if (Long.class.equals(value.getClass())) {
                return Long.class;
            }
            return String.class;
        }
        if (Ihr360EntityFieldType.STRING.equals(type)) {
            return String.class;
        }
        if (Ihr360EntityFieldType.BOOLEAN.equals(type)) {
            return Boolean.class;
        }
        if (Ihr360EntityFieldType.SMALL_BOOLEAN.equals(type)) {
            return boolean.class;
        }
        if (Ihr360EntityFieldType.LONG.equals(type)) {
            return Long.class;
        }
        return String.class;
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        //当前查询root跟对象Courier
        if (CollectionUtils.isEmpty(predications)) {
            return null;
        }

        Map<String, List<Predicate>> predicateMap = new HashMap<>();
        for (Ihr360Predication predication : predications) {
            if (!hasField(predication)) {
                continue;
            }
            String fieldName = predication.getFieldName();
            Object fieldValue = predication.getFieldValue();
            Class fieldClazz = getFiledClass(fieldValue, predication.getFieldType());

            Predicate pre = getPredicate(root, cb, predication.getOperator(), fieldName, fieldValue, fieldClazz);

            if (!predicateMap.containsKey(fieldName)) {
                predicateMap.put(fieldName, new ArrayList<>());
            }
            predicateMap.get(fieldName).add(pre);
        }

        List<Predicate> list = new ArrayList<>();
        Collection<List<Predicate>> values = predicateMap.values();
        values.forEach(item -> {
            int size = item.size();
            if (size == 1) {
                list.add(item.get(0));
            } else if (size > 1) {
                Predicate or = cb.or(item.toArray(new Predicate[0]));
                list.add(or);
            }
        });
        return cb.and(list.toArray(new Predicate[0]));
    }

    private Predicate getPredicate(Root<T> root, CriteriaBuilder cb, PredicationOperator operator, String fieldName, Object fieldValue, Class fieldClazz) {
        switch (operator) {
            case NOT_EQUALS:
                if (fieldValue == null) {
                    return cb.isNotNull(getPath(root, fieldName));
                } else {
                    return cb.notEqual(getPath(root, fieldName).as(fieldClazz), fieldValue);
                }
            case EQUALS:
                if (fieldValue == null) {
                    return cb.isNull(getPath(root, fieldName));
                } else {
                    return cb.equal(getPath(root, fieldName).as(fieldClazz), fieldValue);
                }
            case START_WITH:
                return cb.like(getPath(root, fieldName).as(fieldClazz),
                        fieldValue + "%");
            case END_WITH:
                return cb.like(getPath(root, fieldName).as(fieldClazz),
                        "%" + fieldValue);
            case CONTAINS:
                return cb.like(getPath(root, fieldName).as(fieldClazz),
                        "%" + fieldValue + "%");
            case IN: {
                Expression<T> path = getPath(root, fieldName);
                CriteriaBuilder.In predicate = cb.in(path);
                Set<Object> values = (Set) fieldValue;
                for (Object o:values){
                    predicate.value(o);
                }
                return predicate;
            }
            default:
                return cb.equal(getPath(root, fieldName).as(fieldClazz), fieldValue);
        }
    }

    private Path<T> getPath(Root<T> root, String fieldName) {
        if (fieldName.contains(TABLE_AND_FIELD_SEPARATER)) {
            String[] fields = fieldName.split(TABLE_AND_FIELD_SPLIT);
            Path<T> path = root;
            for (String field : fields) {
                path = path.get(field);
            }
            return path;
        }
        return root.get(fieldName);
    }
}